<html>
<!--
 * WiFi Keyboard - Remote Keyboard for Android.
 * Copyright (C) 2011 Ivan Volosyuk
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 -->
<head>
  <!-- Todo list:
     - Latency display enable.
     - Support fo chinese input method, need to figure out how to deal with it
     - TextField mode
     - 

    -->
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title>WiFi Keyboard</title>
  <script language="JavaScript">
   if (typeof XMLHttpRequest == "undefined")
     XMLHttpRequest = function () {
       try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); }
       catch (e) {}
       try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); }
       catch (e) {}
       try { return new ActiveXObject("Msxml2.XMLHTTP"); }
       catch (e) {}
       //Microsoft.XMLHTTP points to Msxml2.XMLHTTP.3.0 and is redundant
       throw new Error("This browser does not support XMLHttpRequest.");
   };

    // format: /seq,code,press/5012,32,1/
    ignore_next_char = false;
    repeat_key_code = "-1";

    seqConfirmed = 12345;
    seqPressed = seqConfirmed;
    events=Object();
    num_inflight = 0;
    num_delayed = 0;
    latency = 0;
    latency2 = 0;
    latencyTime = 0;
    latencyTime2 = 0;
    displayState = "loading";
    stat = null;
    inp = null;
    typed_chars = 0;
    session = null;


    function onConfirm(newConfirmed) {
      if (newConfirmed == seqPressed) {
        events=Object();
      }
      if (newConfirmed <= seqConfirmed) return;
      for (i = seqConfirmed; i < newConfirmed; i++) {
        events[i] = undefined;
	seqConfirmed = newConfirmed;
      }
      network();
    }

    function queue(code, mode) {
      ev = mode + code;
      events[seqPressed] = ev;
      seqPressed += 1
//      var el = document.getElementById("keys");
//      el.innerHTML = el.innerHTML + ' send:' + mode + code + " ";
    }

    function send(code, mode) {
      queue(code, mode);
      network();
    }

    function makeNotificationListener() {
      receiver = new XMLHttpRequest();
      receiver.onreadystatechange = function() {
	if (receiver.readyState != 4) return;
        if (receiver.status != 200) {
	  // FIXME: error handling
	  setTimeout("makeNotificationListener();", 2000);
	}
        if (receiver.responseText != undefined) {
          textarea = document.getElementById("text");
	  textarea.value = receiver.responseText;
	  textarea.focus();
	}
	makeNotificationListener();
      }
      receiver.open('GET', "/wait", true);
      receiver.send(null);
    }

    // makeNotificationListener();

    function makeXhr() {
      var xhr = new XMLHttpRequest();
      var sentNewConfirmed = newConfirmed;
      var aborted = false;
      var finished = false;
      num_inflight += 1;
      xhr.onreadystatechange = function() {
	if (aborted) return;
	if (xhr.readyState != 4) return;
	num_inflight -= 1;
	finished = true;

        if (xhr.status != 200) {
	  setDisplayState("failure")
	  if (num_delayed == 0) {
	    num_delayed = 1;
            setTimeout("networkDelayed();", 2000);
	  }
	  return;
        }
        if (xhr.responseText == "problem") {
	  setDisplayState("problem");
	} else if (xhr.responseText == "denied") {
	  session = null;
        } else if (xhr.responseText == "multi") {
	  setDisplayState("multi");
        } else {
	  setDisplayState("connected");
        }
	onConfirm(sentNewConfirmed);
      }
      setTimeout(function() {
        if (finished) return;
	aborted = true;
	xhr.abort();
	num_inflight -= 1;
	setDisplayState("failure");
	makeXhr();
      }, 3000);
      xhr.open('GET', "/key?" + session + "," + sentNewConfirmed + "," + data, true);
      xhr.send(null);
      return xhr;
    }

    function initSession() {
      var xhr = new XMLHttpRequest();
      num_inflight += 1;
      xhr.onreadystatechange = function() {
	if (xhr.readyState != 4) return;
	num_inflight -= 1;

        if (xhr.status != 200) {
	  setDisplayState("failure")
	  if (num_delayed == 0) {
	    num_delayed = 1;
            setTimeout("networkDelayed();", 2000);
	  }
	  return;
        }
        if (xhr.responseText == "denied") {
	  setDisplayState("denied");
        } else {
	  session = xhr.responseText;
	  network();
        }
      }
      xhr.open('GET', "/init", true);
      xhr.send(null);
    }

    function network() {
      if (seqConfirmed == seqPressed) return;
      if (num_inflight > 0) return;
      if (session == null) {
        initSession();
      	return;
      }

      //seq = seq + 1;
      //if (data.length > 200) data = data.substring(0, 200);

      charsToTransmit = seqPressed - seqConfirmed;
      if (charsToTransmit > 100) charsToTransmit = 100;
      data = events[seqConfirmed] + ",";
      newConfirmed = seqConfirmed + charsToTransmit;
      for (i = seqConfirmed + 1; i < newConfirmed; i++) {
        data = events[i] + "," + data;
      }

      var xhr = makeXhr();
    }

    function networkDelayed() {
      num_delayed = 0;
      network();
    }

    function compat() {
      return !document.f.mode[0].checked;
    }

    function is_special(code) {
      if (code <= 40) {
        if (code >= 33) return true;
	if (code == 8 || code == 9 || code == 13 || code == 27) return true;
	if (code >= 16 && code <= 20) return true;
      } else {
        if (code == 42 || code == 45 || code == 46 || code == 93 || code == 145) return true;
	if (code >= 112 && code <= 123) return true;
      }
      return false;
    }
    function keyevent(key, mode) {
      if (compat()) {
        ignore_next_char = mode == 'D';
        repeat_key_code = key;
        send(key, mode);
	return false;
      }
      if (is_special(key)) {
        ignore_next_char = mode == 'D';
        repeat_key_code = key;
        send(key, mode);
	return false;
      }
      ignore_next_char = false;
      return true;
    }

    function update_input() {
      typed_chars++;
    }

    function examine_input() {
      if (inp == null) {
        inp = document.getElementById("in");
      }
      if (inp == null) {
        typed_chars = 0;
        return;
      }
      var val = inp.value;
      if (val.length == 0) {
	typed_chars = 0;
        return;
      }

      // if not paste and not chinese
      if (val.length <= typed_chars
        && val.charCodeAt(0) < 128) {
	return;
      }
      for (i = 0; i < val.length; i++) {
        queue(val.charCodeAt(i), 'C');
      }
      typed_chars = 0;
      inp.value = "";
      network();
    }

    function up(e) {
      examine_input();
      if (!e) e = window.event;
      return keyevent(e.keyCode, 'U');
    }
    function down(e) {
      update_input();
      if (!e) e = window.event;
      if (e.keyCode == 115) {
        no_input();
        recv_text();
        return false;
      }
      return keyevent(e.keyCode, 'D');
    }
    function press(e) {
      if (ignore_next_char) {
         ignore_next_char = false;
         return false;
      }
      if (compat()) return false;
      if (!e) e = window.event;
      if (e.charCode != undefined) {
        ch = e.charCode;
      } else {
        ch = e.keyCode;
      }

      // firefox and opera hack
      if (ch == 0 || ch == repeat_key_code) {
        send(repeat_key_code, 'D');
        return false;
      }
      send(ch, 'C');
      return false;
    }
    function focus_me() {
      document.getElementById("in").focus();
    }

    function setDisplayState(newState) {
      if (newState == displayState) return;
      displayState = newState;

      if (stat == null) {
        stat = document.getElementById("status");
        xcomment = document.getElementById("comment");
      }
      if (stat == null) return;

      if (newState == "connected") {
        stat.innerHTML = "Connected";
        stat["color"] = "green";
        xcomment.innerHTML = "You can type now.";
      } else if (newState == "failure") {
        stat.innerHTML = "Connection problem";
        stat["color"] = "red";
        xcomment.innerHTML = "No connection between the phone and computer.";
      } else if (newState == "denied") {
        stat.innerHTML = "Session busy";
        stat["color"] = "red";
        xcomment.innerHTML = 'Press "Reset session" in WiFi Keyboard window.';
      } else if (newState == "problem") {
        stat.innerHTML = "Not typing";
        stat["color"] = "red";
        xcomment.innerHTML = 'Enable WiFiKeyboard input on you device. <a href="http://code.google.com/p/wifikeyboard/wiki/WiFiKeyboardSettings">Visit help page.</a>';
      } else if (newState == "multi") {
        stat.innerHTML = "Mutiple input";
        stat["color"] = "red";
        xcomment.innerHTML = "Typing from multiple browser windows is not supported. Use one browser window.";
      }
    }

    function submit_text() {
        var submitter = new XMLHttpRequest();
        submitter.onreadystatechange = function() {
  	  if (submitter.readyState != 4) return;
            if (submitter.status != 200 || submitter.responseText != "ok") {
  	    // FIXME: error handling
  	    setTimeout("submit_text();", 2000);
          } else {
            document.getElementById('in').focus();
            direct_input();
  	  }
        }
        var params = document.getElementById("in").value;
        submitter.open('POST', "/form", true);
        submitter.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
        submitter.setRequestHeader("Content-length", params.length);
        submitter.send(params);
      }

    function direct_input() {
        var x = document.getElementById("in");
        x.onkeydown = down;
        x.onkeyup = up;
        x.onkeypress = press;
        window.onblur = function() {
            send(1024,'D');
            focus_me();
        }
        window.onfocus = focus_me;
        window.onmouseup = function() {
            setTimeout('examine_input();', 10);
        };
        window.onmouseover = function() {
            setTimeout('examine_input();', 10);
        };
        n = document.getElementById("in");
        n.style.backgroundColor = "#f1f1ed";
        n.value = "";
        
    }

    function no_input() {
        document.getElementById("in").style.backgroundColor = "#f1e1cd";
        var x = document.getElementById("in");
        x.onkeydown = function(e) { return false; }
        x.onkeypress = function() { return false; }
        x.onkeyup = function() { return false; }
        window.onblur = function() {}
        window.onmouseup = function() {};
        window.onmouseover = function() {};
        
    }

    function local_input() {
        document.getElementById("in").style.backgroundColor = "#FFFFFF";
        var x = document.getElementById("in");
        x.onkeydown = function(e) {
            if (!e) e = window.event;
            if (e.keyCode == 115) {
                submit_text();
                no_input();
                return false;
            }
          return true;
        }
        x.onkeypress = function() { return true; }
        x.onkeyup = function() { return true; }
    }

    function recv_text() {
        var recvText = new XMLHttpRequest();
        recvText.onreadystatechange = function() {
  	  if (recvText.readyState != 4) return;
            if (recvText.status != 200) {
  	    // FIXME: error handling
  	    setTimeout("recv_text();", 2000);
          }
          if (recvText.responseText != undefined) {
              document.getElementById('in').value = recvText.responseText;
              local_input();
  	  }
        }
        recvText.open('GET', "/text", true);
        recvText.send(null);
    }
    

    function loadFinished() {
      focus_me();
      send(1024, 'D');
      direct_input();
      (function() {
    	     var wf = document.createElement('script');
    	     wf.src = ('https:' == document.location.protocol ? 'https' : 'http') +
    	         '://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js';
    	     wf.type = 'text/javascript';
    	     wf.async = 'true';
    	     wf.onload = function() {
    	         WebFont.load({
    	             google: {
    	               families: [ 'Cantal', 'Yanone Kaffeesatz', 'Droid Sans' ]
    	          }});
    	     }
    	     var s = document.getElementsByTagName('script')[0];
    	     s.parentNode.insertBefore(wf, s);
    	   })();
    }
    window.onload = loadFinished;
</script>
<link rel="icon" type="image/png" href="/icon.png" />
<style type="text/css">
   h1 {
    font-size: 30px;
   } 
   .wf-active h1 {
    font-family: 'Cantal', arial, serif;
   }
   h2 {
    font-size: 20px;
   }
   .wf-active h2 {
    font-family: 'Yanone Kaffeesatz', arial, serif; 
   }
   div {
    font-size: 15px;
    padding: 3px;
   }
   .wf-active div {
    font-family: 'Droid Sans', arial, serif;
   }

   div.footer {
    font-family: arial, serif;
    font-size: 20px;
    text-align: right;
   }
   
   body {
    color:#000000;
    background-color:#f1f1ed;
    margin:0px;
    overflow:auto;
   }
   table.main {
     width:800px;
     margin-top:0px;
     margin-bottom:0px;
     background-color:#ffffff;
     padding:15px;
     background-image:url('bg.gif');
     background-repeat:repeat-x;
     height: 100%;
   }
</style>
</head>
<body id="myBody">
<table class="main" align="center">
<tbody>
<tr valign="top">
<td class="main" width="450">
<!-- Main Window -->
<h1>WiFi&nbsp;Keyboard</h1>
  <table border="0"><tbody><tr><td cellspacing="50" cellpadding="50">
  <form name="f">
  <h2>Keyboard mode:</h2>
  <div class="mode"><input type="radio" name="mode" value="i" checked>International keyboard</div>
  <div class="mode"><input type="radio" name="mode" value="c">Game mode (key codes only)</div>
  </form>
  </td></tr></tbody></table>

<!-- Main Window End -->
</td>
<td width="450">
  <table border="0"><tbody><tr><td cellspacing="50" cellpadding="50">
  <tr>
  <td>
  <h2>Special keys:</h2>
  <ul>
  <li>Shift = Shift</li>
  <li>Alt = Alt</li>
  <li>Arrows = DPAD Arrows</li>
  <li>Insert or F1 = DPAD Center</li>
  <li>F2 = Menu</li>
  <li>F3 = Search</li>
  <li>Escape,F5 = BACK</li>
  <li>F9, F10 = Volume control</li>
  <li><font color="red">F4 = Edit locally/ submit (alpha)</font></li>
  </ul>
  </td></tr></tbody></table>
</td>
<td align="right" width="200">
<!-- Side bar -->
  <table border="1" cellpadding="20"><tbody><tr><td cellspacing="50" cellpadding="50">
  <h2>Status: <font color="black" id="status">Connecting...</font></h2>
  <div id="comment"></div>
  </td></tr></tbody></table>

  <br>
</td>
</tr>
<tr>
<td colspan="3">
  <h2>Android input (type or paste here):</h2>
  <form method="post" action="/form">
      <input type="hidden" name="name" value="value">
      <textarea name="text" cols="100" rows="20" id="in"></textarea>
  </form>

</td>
</tr>
<tr>
<td colspan="3">
  <div class="footer"><a href="http://code.google.com/p/wifikeyboard/">Source code available (license GPL v2).</a><br>Created by Ivan Volosyuk on 2010.</div>
<!-- <div id="keys">Debug: key presses:</div> -->
<!-- <div id="debug">New event placeholder.</div> -->
<td>
</tr>
</tbody>
</table>
</body>
</html>
